package tinybase.sm;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Comparator;

import tinybase.basic.BytePointer;
import tinybase.basic.RC;
import tinybase.basic.RedBase;
import tinybase.basic.RedBase.AttrType;
import tinybase.basic.RedBase.ClientHint;
import tinybase.basic.RedBase.CompOp;
import tinybase.ix.IX_Manager;
import tinybase.ix.IndexHandle;
import tinybase.ix.IndexScan;
import tinybase.rm.RID;
import tinybase.rm.RM_FileHandle;
import tinybase.rm.RM_FileScan;
import tinybase.rm.RM_Manager;
import tinybase.rm.RM_Record;

public class SM_Manager {
	// Public

	/**
	 * 
	 * @param ixm
	 *            : inout
	 * @param rmm
	 *            : inout
	 */
	public SM_Manager(IX_Manager ixm, RM_Manager rmm) {
		pIxm = ixm;
		pRmm = rmm;

		//
		useIndexNo = -1;
	}

	String currDirectory;

	String getAbsoluteFilePath(String filename) {
		return currDirectory + filename;
	}

	/**
	 * 
	 * @param dbName
	 *            : in
	 * @return
	 */
	public RC openDb(String dbName) {
		RC rc;

		// Sanity Check: Length of the argument should be less than MAXDBNAME
		// DBname cannot contain ' ' or '/'
		if (dbName.length() > SM_Internal.MAXDBNAME || dbName.contains(" ")
				|| dbName.contains("/")) {
			rc = RC.SM_INVALIDDBNAME;
			return rc;
		}

		// Change the working directory
		{
			File db = new File(dbName);
			if (!db.isDirectory()) {
				rc = RC.SM_CHDIRFAILED;
				return rc;
			}
			this.currDirectory = db.getAbsoluteFile().getPath();
		}

		// Open a file scan for RELCAT
		if ((rc = pRmm.openFile(this.getAbsoluteFilePath(SM_Internal.RELCAT),
				fhRelcat)) != RC.SUCCESS)
			return rc;

		// Open a file scan for ATTRCAT
		if ((rc = pRmm.openFile(this.getAbsoluteFilePath(SM_Internal.ATTRCAT),
				fhAttrcat)) != RC.SUCCESS) {
			pRmm.closeFile(fhRelcat);
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	} // Open the database

	public RC closeDb() {
		RC rc;

		// Close a file scan for ATTRCAT
		if ((rc = pRmm.closeFile(fhAttrcat)) != RC.SUCCESS) {
			pRmm.closeFile(fhRelcat);
			return rc;
		}

		// Close a file scan for RELCAT
		if ((rc = pRmm.closeFile(fhRelcat)) != RC.SUCCESS)
			return rc;

		// Return ok
		return RC.SUCCESS;
	} // close the database

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param attRCount
	 *            : in
	 * @param attrInfos
	 *            : in
	 * @return
	 */
	public RC createTable(String relName, int attrCount, AttrInfo[] attributes) {
		RC rc;
		RM_Record tmpRec = new RM_Record();
		BytePointer relcatData = new BytePointer();
		int tupleLength = 0;
		int offset = 0;
		SM_RelcatRec relcatRec = new SM_RelcatRec(null, 0, 0, 0);
		SM_AttrcatRec attrcatRec = new SM_AttrcatRec(null, null, 0, null, 0, 0);
		RID rid = new RID();

		// Sanity Check: relName should not be RELCAT or ATTRCAT
		if (relName.equals(SM_Internal.RELCAT)
				|| relName.equals(SM_Internal.ATTRCAT)) {
			rc = RC.SM_INVALIDRELNAME;
			return rc;
		}

		// Compute tupleLength by summing up attrLength
		// Sanity Check: duplicated attribute names
		for (int i = 0; i < attrCount; i++) {
			tupleLength += attributes[i].attrLength;
			for (int j = i + 1; j < attrCount; j++) {
				if (attributes[i].pAttrName.equals(attributes[j].pAttrName)) {
					rc = RC.SM_DUPLICATEDATTR;
					return rc;
				}
			}
		}

		// Sanity Check: relName should not exist
		if ((rc = getRelationInfo(relName, tmpRec, relcatData)) != RC.SM_RELNOTFOUND) {
			if (rc == RC.SUCCESS)
				rc = RC.SM_RELEXISTS;
			return rc;
		}

		// Update RELCAT
		SM_RelcatRec.SM_SetRelcatRec(relcatRec, relName, tupleLength,
				attrCount, 0);
		ByteBuffer relcatRecBuf = ByteBuffer.allocate(SM_RelcatRec
				.getSM_RelcatRecSize());
		relcatRec.storeAsBytes(relcatRecBuf);
		if ((rc = fhRelcat.insertRec(relcatRecBuf.array(), rid)) != RC.SUCCESS) {
			return rc;
		}
		if ((rc = fhRelcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS)
			return rc;

		ByteBuffer attrcatBuf = ByteBuffer.allocate(SM_AttrcatRec
				.getSM_AttrcatRecSize());
		// Update ATTRCAT
		for (int i = 0; i < attrCount; i++) {
			SM_AttrcatRec.SM_SetAttrcatRec(attrcatRec, relName,
					attributes[i].pAttrName, offset, attributes[i].attrType,
					attributes[i].attrLength, -1);
			offset += attributes[i].attrLength;
			attrcatRec.storeAsBytes(attrcatBuf);
			if ((rc = fhAttrcat.insertRec(attrcatBuf.array(), rid)) != RC.SUCCESS) {
				return rc;
			}
		}
		if ((rc = fhAttrcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS) {
			return rc;
		}

		// Create file
		if ((rc = pRmm.createFile(relName, tupleLength)) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	} // attribute data

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param attrName
	 *            : in
	 * @return
	 */
	public RC createIndex(String relName, String attrName) {
		RC rc;
		RM_Record rec = new RM_Record();
		BytePointer attrcatData = new BytePointer();
		int indexNo = -1;
		RM_FileScan fs = new RM_FileScan();
		RM_FileHandle fh = new RM_FileHandle();
		RM_Record dataRec = new RM_Record();
		IndexHandle ih = new IndexHandle();

		// Sanity Check: relName/attrName should exist, but its index should not
		if ((rc = getAttributeInfo(relName, attrName, rec, attrcatData)) != RC.SUCCESS)
			return rc;
		if (ByteBuffer.wrap(attrcatData.getArray()).getInt(
				SM_AttrcatRec.getIndexNoOffset()) != -1) {
			rc = RC.SM_INDEXEXISTS;
			return rc;
		}
		// Determine indexNo
		indexNo = ByteBuffer.wrap(attrcatData.getArray()).getInt(
				SM_AttrcatRec.getOffsetOffset());

		// Build index
		AttrType at = AttrType.INT;
		at.setValue(ByteBuffer.wrap(attrcatData.getArray()).getInt(
				SM_AttrcatRec.getAttrTypeOffset()));
		if ((rc = pIxm.createIndex(relName, indexNo, at, ByteBuffer.wrap(
				attrcatData.getArray()).getInt(
				SM_AttrcatRec.getAttrLengthOffset()))) != RC.SUCCESS) {
			return rc;
		}
		if ((rc = pIxm.openIndex(relName, indexNo, ih)) != RC.SUCCESS) {
			pIxm.destroyIndex(relName, indexNo);
			return rc;
		}

		if ((rc = pRmm.openFile(relName, fh)) != RC.SUCCESS) {
			pIxm.closeIndex(ih);
			pIxm.destroyIndex(relName, indexNo);
			return (rc);
		}

		if ((rc = fs.openScan(fh, AttrType.INT, 4, 0, CompOp.NO_OP, null,
				RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
			pRmm.closeFile(fh);
			pIxm.closeIndex(ih);
			pIxm.destroyIndex(relName, indexNo);
			return (rc);
		}

		while ((rc = fs.getNextRec(dataRec)).getValue() != RC.RM_EOF.getValue()) {
			BytePointer pData = new BytePointer();
			RID rid = new RID();

			if (rc != RC.SUCCESS) {
				fs.closeScan();
				pRmm.closeFile(fh);
				pIxm.closeIndex(ih);
				pIxm.destroyIndex(relName, indexNo);
				return (rc);
			}

			if ((rc = dataRec.getData(pData)) != RC.SUCCESS) {
				fs.closeScan();
				pRmm.closeFile(fh);
				pIxm.closeIndex(ih);
				pIxm.destroyIndex(relName, indexNo);
				return (rc);
			}

			if ((rc = dataRec.getRid(rid)) != RC.SUCCESS) {
				fs.closeScan();
				pRmm.closeFile(fh);
				pIxm.closeIndex(ih);
				pIxm.destroyIndex(relName, indexNo);
				return (rc);
			}

			if ((rc = ih.insertEntry((Object) pData.getArray(ByteBuffer.wrap(
					attrcatData.getArray()).getInt(
					SM_AttrcatRec.getOffsetOffset())), rid)) != RC.SUCCESS) {
				fs.closeScan();
				pRmm.closeFile(fh);
				pIxm.closeIndex(ih);
				pIxm.destroyIndex(relName, indexNo);
				return (rc);
			}
		}

		if ((rc = fs.closeScan()) != RC.SUCCESS) {
			pRmm.closeFile(fh);
			pIxm.closeIndex(ih);
			pIxm.destroyIndex(relName, indexNo);
			return rc;
		}

		if ((rc = pRmm.closeFile(fh)) != RC.SUCCESS) {
			pIxm.closeIndex(ih);
			pIxm.destroyIndex(relName, indexNo);
			return rc;
		}

		ByteBuffer buf = ByteBuffer.wrap(attrcatData.getArray());
		int off = 0;
		// off= SM_AttrcatRec.getDistinctValsOffset();
		// buf.putInt(off, buf.getInt(off) + ih.deltaDistinctVals());
		//
		// off= SM_AttrcatRec.getIxDepthOffset();
		// buf.putInt(off, buf.getInt(off) + ih.deltaDepth());
		//		   
		// if ((rc= ih.getMinKey(new IntegerWrapper(buf.getInt(
		// SM_AttrcatRec.getMinValueOffset())), new PageNum(0)))
		// != RC.SUCCESS)
		// pIxm.destroyIndex(relName, indexNo);
		//			   
		//		  
		// if ((rc= ih.getMaxKey(new IntegerWrapper(buf.getInt(
		// SM_AttrcatRec.getMaxValueOffset())), new PageNum(0)))
		// != RC.SUCCESS)
		// pIxm.destroyIndex(relName, indexNo);

		if ((rc = pIxm.closeIndex(ih)) != RC.SUCCESS) {
			pIxm.destroyIndex(relName, indexNo);
			return rc;
		}

		// Update indexNo
		off = SM_AttrcatRec.getIndexNoOffset();
		buf.putInt(off, indexNo);

		if ((rc = fhAttrcat.updateRec(rec)) != RC.SUCCESS) {
			return rc;
		}

		if ((rc = fhAttrcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS) {
			return rc;
		}

		// update RELCAT
		if ((rc = setRelationIndexCount(attrName, +1)) != RC.SUCCESS)
			return rc;

		// Return ok
		return RC.SUCCESS;
	} // relName.attrName

	/**
	 * 
	 * @param relName
	 *            : in
	 * @return
	 */
	public RC dropTable(String relName) {
		RC rc = RC.SUCCESS;
		RM_Record rec = new RM_Record();
		RM_Record _rec = new RM_Record();
		BytePointer relcatData = new BytePointer();
		RID rid = new RID();
		byte[] _relName = new byte[RedBase.MAXNAME];
		RM_FileScan fs = new RM_FileScan();
		int i = 0;
		ByteBuffer buf = null;

		// Sanity Check: relName should not be RELCAT or ATTRCAT
		if (relName.equals(SM_Internal.RELCAT)
				|| relName.equals(SM_Internal.ATTRCAT)) {
			rc = RC.SM_INVALIDRELNAME;
			return rc;
		}

		// Update RELCAT
		if ((rc = getRelationInfo(relName, rec, relcatData)) != RC.SUCCESS)
			return rc;

		if ((rc = rec.getRid(rid)) != RC.SUCCESS)
			return rc;

		if ((rc = fhRelcat.deleteRec(rid)) != RC.SUCCESS)
			return rc;

		if ((rc = fhRelcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS)
			return rc;

		// Update ATTRCAT
		Arrays.fill(_relName, (byte) '\0');
		_relName = Arrays.copyOf(relName.getBytes(Charset.forName("ASCII")), RedBase.MAXNAME);
		if ((rc = fs.openScan(fhAttrcat, AttrType.STRING, RedBase.MAXNAME,
				SM_AttrcatRec.getRelNameOffset(), CompOp.EQ_OP, _relName,
				ClientHint.NO_HINT)) != RC.SUCCESS)
			return rc;

		while ((rc = fs.getNextRec(_rec)) != RC.RM_EOF) {
			BytePointer attrcatData = new BytePointer();

			if (rc != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}

			// Delete the index on this attribute, if any
			if ((rc = rec.getData(attrcatData)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}

			buf = ByteBuffer.wrap(attrcatData.getArray());
			if (buf.getInt(SM_AttrcatRec.getIndexNoOffset()) != -1)
				pIxm.destroyIndex(relName, buf.getInt(SM_AttrcatRec
						.getIndexNoOffset()));

			// Delete the record from ATTRCAT
			if ((rc = _rec.getRid(rid)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
			if ((rc = fhAttrcat.deleteRec(rid)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}

			if (++i == buf.getInt(SM_RelcatRec.getAttrCountOffset()))
				break;
		}

		if ((rc = fs.closeScan()) != RC.SUCCESS)
			return rc;
		if ((rc = fhAttrcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS)
			return rc;

		// Destroy file
		if ((rc = pRmm.destroyFile(relName)) != RC.SUCCESS)
			return rc;

		// Return ok
		return RC.SUCCESS;
	} // destroy a relation

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param attrName
	 *            : in
	 * @return
	 */
	public RC dropIndex(String relName, String attrName) {
		RC rc= RC.SUCCESS;
		RM_Record rec= new RM_Record();
		BytePointer attrcatData= new BytePointer();
//		RM_FileScan fs= new RM_FileScan();
		ByteBuffer buf= null;
		
		// Sanity Check: relName/attrName and its index should exist
		if ((rc= getAttributeInfo(relName, attrName, rec, attrcatData))
				!= RC.SUCCESS)
			return rc;
		buf= ByteBuffer.wrap(attrcatData.getArray());
		if (buf.getInt(SM_AttrcatRec.getIndexNoOffset()) == -1) {
			rc= RC.SM_INDEXNOTFOUND;
			return rc;
		}
		
		// Destroy the index file
		if ((rc= pIxm.destroyIndex(relName, buf.getInt(SM_AttrcatRec.getIndexNoOffset())))
				!= RC.SUCCESS)
			return rc;

		// Update indexNo
		buf.putInt(SM_AttrcatRec.getIndexNoOffset(), -1);
		if ((rc= fhAttrcat.updateRec(rec)) != RC.SUCCESS)
			return rc;
		if ((rc= fhAttrcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS)
			return rc;
		
		// Update RELCAT
		if ((rc= setRelationIndexCount(relName, -1))
				!= RC.SUCCESS)
			return rc;
		
		// Return OK
		return RC.SUCCESS;
	} // relName.attrName

	private String strpbrk(String str1, String str2) {
		boolean found= false;
		String result= null;
		for (int i= 0; i < str2.length() && !found; i++) {
			char b= str2.charAt(i);
			for (int j= 0; j < str1.length(); j++) {
				if (b == str1.charAt(j)) {
					found= true;
					result= str1.substring(j);
					break;
				}
			}
		}
		return result;
	}
	//
	// compareDataAttrInfo

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param fileName
	 *            : in
	 * @return
	 */
	public RC load(String relName, String fileName) throws IOException {
		RC rc= RC.SUCCESS;
		RM_Record tmpRec= new RM_Record();
		BytePointer relcatData= new BytePointer();
		byte[] _relName= new byte[RedBase.MAXNAME];
		SM_AttrcatRec[] attributes= null;
		IndexHandle[] ihs= null;
		RM_FileHandle fh= new RM_FileHandle();
		RM_FileScan fs= new RM_FileScan();
		RM_Record rec= new RM_Record();
		BytePointer data= new BytePointer();
		File f= null;
		RandomAccessFile fp= null;
		byte[] buf= null;
		int i= 0;
		ByteBuffer buffer= null;
		
		
		// Sanity Check: relName should not be RELCAT or ATTRCAT
		if (relName.equals(SM_Internal.RELCAT) || relName.equals(SM_Internal.ATTRCAT)) {
			rc= RC.SM_INVALIDRELNAME;
			return rc;
		}

		// Get the attribute count
		if ((rc= getRelationInfo(relName, tmpRec, relcatData)) != RC.SUCCESS)
			return rc;
		
		buffer= ByteBuffer.wrap(relcatData.getArray());
		
		// Allocate indexhandle array
		ihs= new IndexHandle[buffer.getInt(SM_RelcatRec.getAttrCountOffset())];
		if (ihs == null) {
			rc= RC.SM_NOMEM;
			return rc;
		}

		// Allocate attributes array
		attributes= new SM_AttrcatRec[buffer.getInt(SM_RelcatRec.getAttrCountOffset())];
		if (attributes == null) {
			rc= RC.SM_NOMEM;
			return rc;
		}
		
		// Allocate buffer
		buf= new byte[SM_Internal.MAXLINE];
		if (buf == null) {
			rc= RC.SM_NOMEM;
			return rc;
		}

		// Allocate data
		data= new BytePointer(new byte[buffer.getInt(SM_RelcatRec.getTupleLengthOffset())]);
		if (data == null) {
			rc= RC.SM_NOMEM;
			return rc;
		}

		// Open a file scan for ATTRCAT
		Arrays.fill(_relName, (byte)'\0');
		_relName= Arrays.copyOf(relName.getBytes(Charset.forName("ASCII")), RedBase.MAXNAME);
		if ((rc= fs.openScan(fhAttrcat, AttrType.STRING, RedBase.MAXNAME, 
				SM_AttrcatRec.getRelNameOffset(), CompOp.EQ_OP, _relName, 
				ClientHint.NO_HINT)) != RC.SUCCESS)
			return rc;
		
		// Fill out attributes array
		while ((rc= fs.getNextRec(rec)) != RC.RM_EOF) {
			BytePointer _data= new BytePointer();
			
			if (rc != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
			if ((rc= rec.getData(_data)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
			
			attributes[i]= new SM_AttrcatRec(Arrays.copyOf(_data.getArray(), SM_AttrcatRec.getSM_AttrcatRecSize()));
			if (++i == buffer.getInt(SM_RelcatRec.getAttrCountOffset()))
				break;
		}
		
		// Close a file scan for ATTRCAT
		if ((rc= fs.closeScan()) != RC.SUCCESS)
			return rc;
		
		// Open data file
		f= new File(fileName);
		fp= new RandomAccessFile(f, "r");
		if (fp == null) {
			rc= RC.SM_FILEIOFAILED;
			return rc;
		}
		
		// Open relation file
		if ((rc= pRmm.openFile(relName, fh)) != RC.SUCCESS) {
			fp.close();
			return rc;
		}

		// Open indexes
		for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++) {
			if (attributes[i].indexNo == -1)
				continue;
			if ((rc= pIxm.openIndex(relName, attributes[i].indexNo, ihs[i]))
					!= RC.SUCCESS) {
				for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++)
					if (attributes[i].indexNo != -1)
						pIxm.closeIndex(ihs[i]);
				pRmm.closeFile(fh);
				fp.close();
				return rc;
			}
		}
		
		// Process every line
		while ((buf= fp.readLine().getBytes(Charset.forName("ASCII"))) != null) {
			int numDelim= 0;
			BytePointer attr= new BytePointer(buf);
			RID rid= null;
			
			// Count commas and confirm the whole line was read
			for (i= 0; i < SM_Internal.MAXLINE - 1; i++) {
				if (buf[i] == '\n')
					break;
				else if (buf[i] == ',')
					numDelim++;
			}
			
			// Allow blank lines
			if (i == 0)
				continue;
			
			if (buf[i] != '\n' || buf[i+1] != '\0' || numDelim + 1
					!= buffer.getInt(SM_RelcatRec.getAttrCountOffset())) {
				rc= RC.SM_INVALIDFORMAT;
				for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++)
					if (attributes[i].indexNo != -1)
						pIxm.closeIndex(ihs[i]);
				pRmm.closeFile(fh);
				fp.close();
				return rc;
			}	
			
			// Make record data
			for (i = 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++) {
//				int _i= 0;
//				float _f= 0;
				byte[] delim= strpbrk(attr.getArray().toString(), ",\n").getBytes(
						Charset.forName("ASCII"));
				delim[0]= '\0';
				
				switch (attributes[i].attrType) {
				case INT:
//					_i= ByteBuffer.wrap(attr.getArray()).getInt();
					System.arraycopy(attr, 0, data.getArray(), attributes[i].offset, 4);
					break;
				case FLOAT:
//					_f= ByteBuffer.wrap(attr.getArray()).getFloat();
					System.arraycopy(attr, 0, data.getArray(), attributes[i].offset, 4);
					break;
				case STRING:
					Arrays.fill(data.getArray(), attributes[i].offset, 
							attributes[i].offset + attributes[i].attrLength, (byte)'\0');
					System.arraycopy(attr, 0, data.getArray(), attributes[i].offset, 
							attributes[i].attrLength);
					break;
				}
				attr.setArray(Arrays.copyOfRange(delim, 1, delim.length));
			}
			
			// Insert the record
			if ((rc= fh.insertRec(data.getArray(), rid)) != RC.SUCCESS) {
				for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++)
					if (attributes[i].indexNo != -1)
						pIxm.closeIndex(ihs[i]);
				pRmm.closeFile(fh);
				fp.close();
				return rc;
			}
			
			// Update indexes
			for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++) {
				if (attributes[i].indexNo == -1) 
					continue;
				if ((rc= ihs[i].insertEntry(data.getArray(attributes[i].offset), rid))
						!= RC.SUCCESS) {
					for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++)
						if (attributes[i].indexNo != -1)
							pIxm.closeIndex(ihs[i]);
					pRmm.closeFile(fh);
					fp.close();
					return rc;
				}
			}
		}
		  
		// Close indexes
		for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++) {
			if (attributes[i].indexNo == -1)
				continue;
			if ((rc= pIxm.closeIndex(ihs[i])) != RC.SUCCESS) {
				for (i= 0; i < buffer.getInt(SM_RelcatRec.getAttrCountOffset()); i++)
					if (attributes[i].indexNo != -1)
						pIxm.closeIndex(ihs[i]);
				pRmm.closeFile(fh);
				fp.close();
				return rc;
			}
		}

		// Close relation file
		if ((rc= pRmm.closeFile(fh)) != RC.SUCCESS) {
			fp.close();
			return rc;
		}
		
		// Close data file
		fp.close();

		// Return ok
		return RC.SUCCESS;
	} // fileName

	public RC help() {
		RC rc= RC.SUCCESS;
		DataAttrInfo[] attributes= new DataAttrInfo[4];
		RM_FileScan fs= new RM_FileScan();
		RM_Record rec= new RM_Record();
		
		// Instantiate a Printer object
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[0], 
				SM_Internal.RELCAT, "relName", SM_RelcatRec.getRelNameOffset(), 
				AttrType.STRING, RedBase.MAXNAME, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[1], 
				SM_Internal.RELCAT, "tupleLength", SM_RelcatRec.getTupleLengthOffset(), 
				AttrType.INT, 4, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[2], 
				SM_Internal.RELCAT, "attrCount", SM_RelcatRec.getAttrCountOffset(), 
				AttrType.INT, 4, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[3], 
				SM_Internal.RELCAT, "indexCount", SM_RelcatRec.getIndexCountOffset(), 
				AttrType.INT, 4, -1);
		
		Printer p= new Printer(attributes, 4);

		// Open a file scan for RELCAT
		if ((rc= fs.openScan(fhRelcat, AttrType.INT, 4, 0, CompOp.NO_OP, 
				null, ClientHint.NO_HINT)) != RC.SUCCESS)
			return rc;
		
		// Print the header information
		p.printHeader(System.out);
		
		// Print each tuple
		while ((rc= fs.getNextRec(rec)) != RC.RM_EOF) {
			BytePointer data= new BytePointer();
			if (rc != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
			if ((rc= rec.getData(data)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
			
			p.print(System.out, data.getArray());
		}
		
		// Print the footer information
		p.printFooter(System.out);
		
		// Close a file scan for RELCAT
		if ((rc= fs.closeScan()) != RC.SUCCESS)
			return rc;
		
		// Return OK
		return RC.SUCCESS;
	} // Print relations in db

	/**
	 * 
	 * @param relName
	 *            : in
	 * @return
	 */
	public RC help(String relName) {
		RC rc;
		RM_Record tmpRec = new RM_Record();
		BytePointer relcatData = new BytePointer();
		DataAttrInfo[] attributes = new DataAttrInfo[6];
		byte[] _relName = new byte[RedBase.MAXNAME];
		RM_FileScan fs = new RM_FileScan();
		RM_Record rec = new RM_Record();
		int i = 0;

		// Get the attribute count
		if ((rc = getRelationInfo(relName, tmpRec, relcatData)) != RC.SUCCESS) {
			return (rc);
		}

		// Instantiate a Printer object
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[0], SM_Internal.ATTRCAT,
				"relName", SM_AttrcatRec.getRelNameOffset(), AttrType.STRING,
				RedBase.MAXNAME, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[1], SM_Internal.ATTRCAT,
				"attrName", SM_AttrcatRec.getAttrNameOffset(), AttrType.STRING,
				RedBase.MAXNAME, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[2], SM_Internal.ATTRCAT,
				"offset", SM_AttrcatRec.getOffsetOffset(), AttrType.INT, 4, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[3], SM_Internal.ATTRCAT,
				"attrType", SM_AttrcatRec.getAttrTypeOffset(), AttrType.INT, 4,
				-1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[4], SM_Internal.ATTRCAT,
				"attrLength", SM_AttrcatRec.getAttrLengthOffset(),
				AttrType.INT, 4, -1);
		SM_AttrcatRec.SM_SetAttrcatRec(attributes[5], SM_Internal.ATTRCAT,
				"indexNo", SM_AttrcatRec.getIndexNoOffset(), AttrType.INT, 4,
				-1);
		Printer p = new Printer(attributes, 6);

		// Open a file scan for ATTRCAT
		System.arraycopy(relName.getBytes(Charset.forName("ASCII")), 0,
				_relName, 0, Math.min(relName.length(), RedBase.MAXNAME));
		if ((rc = fs.openScan(fhAttrcat, AttrType.STRING, RedBase.MAXNAME,
				SM_AttrcatRec.getRelNameOffset(), CompOp.EQ_OP, new String(
						_relName, Charset.forName("ASCII")),
				RedBase.ClientHint.NO_HINT)) != RC.SUCCESS)
			return rc;

		// Print the header information
		p.printHeader(System.out);

		// Print each tuple
		ByteBuffer relcatDataBuf = ByteBuffer.wrap(relcatData.getArray());
		while ((rc = fs.getNextRec(rec)) != RC.RM_EOF) {
			BytePointer data = new BytePointer();

			if (rc != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}

			if ((rc = rec.getData(data)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}

			p.print(System.out, data.getArray());

			if (++i == relcatDataBuf.getInt(SM_RelcatRec.getAttrCountOffset()))
				break;
		}

		// Print the footer information
		p.printFooter(System.out);

		// Close a file scan for ATTRCAT
		if ((rc = fs.closeScan()) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	} // print schema of relName

	/**
	 * 
	 * @param relName
	 *            : in
	 * @return
	 */
	public RC print(String relName) {
		RC rc = RC.SUCCESS;
		RM_Record tmpRec = new RM_Record();
		BytePointer relcatData = new BytePointer();
		DataAttrInfo[] attributes = null;
		byte[] _relNameBuf = new byte[RedBase.MAXNAME];
		RM_FileScan fs = new RM_FileScan();
		RM_Record rec = new RM_Record();
		RM_FileHandle fh = new RM_FileHandle();
		int i = 0;

		// Get the attribute count
		if ((rc = getRelationInfo(relName, tmpRec, relcatData)) != RC.SUCCESS) {
			return (rc);
		}

		// Allocate attributes array
		ByteBuffer relcatBuf = ByteBuffer.wrap(relcatData.getArray());
		attributes = new DataAttrInfo[relcatBuf.getInt(SM_RelcatRec
				.getAttrCountOffset())];

		// Open a file scan for ATTRCAT
		Arrays.fill(_relNameBuf, (byte) 0);
		System.arraycopy(relName.getBytes(Charset.forName("ASCII")), 0,
				_relNameBuf, 0, Math.min(relName.length(), RedBase.MAXNAME));
		String _relName = new String(_relNameBuf, Charset.forName("ASCII"));
		if ((rc = fs.openScan(fhAttrcat, AttrType.STRING, RedBase.MAXNAME,
				SM_AttrcatRec.getRelNameOffset(), CompOp.EQ_OP, _relName,
				RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
			return (rc);
		}

		// Fill out attributes array
		byte[] relNameBuf = new byte[RedBase.MAXNAME];
		byte[] attrNameBuf = new byte[RedBase.MAXNAME];
		while ((rc = fs.getNextRec(rec)).getValue() != RC.RM_EOF.getValue()) {
			BytePointer data = new BytePointer();

			if (rc != RC.SUCCESS) {
				fs.closeScan();
				return (rc);
			}

			if ((rc = rec.getData(data)) != RC.SUCCESS) {
				fs.closeScan();
				return (rc);
			}

			ByteBuffer dataBuf = ByteBuffer.wrap(data.getArray());
			Arrays.fill(relNameBuf, (byte) 0);
			Arrays.fill(attrNameBuf, (byte) 0);
			System
					.arraycopy(dataBuf.array(), 0, relNameBuf, 0,
							RedBase.MAXNAME);
			System.arraycopy(dataBuf.array(), RedBase.MAXNAME, attrNameBuf,
					RedBase.MAXNAME, RedBase.MAXNAME);
			dataBuf.getInt(SM_AttrcatRec.getOffsetOffset());
			AttrType _at = AttrType.INT;
			_at.setValue(dataBuf.getInt(SM_AttrcatRec.getAttrTypeOffset()));
			SM_AttrcatRec.SM_SetAttrcatRec(attributes[i], new String(
					relNameBuf, Charset.forName("ASCII")), new String(
					attrNameBuf, Charset.forName("ASCII")), dataBuf
					.getInt(SM_AttrcatRec.getOffsetOffset()), _at, dataBuf
					.getInt(SM_AttrcatRec.getAttrLengthOffset()), dataBuf
					.getInt(SM_AttrcatRec.getIndexNoOffset()));

			if (++i == relcatBuf.getInt(SM_RelcatRec.getAttrCountOffset()))
				break;
		}

		// Close a file scan for ATTRCAT
		if ((rc = fs.closeScan()) != RC.SUCCESS) {
			return (rc);
		}

		// Instantiate a Printer object
		Arrays.sort(attributes, new Comparator<DataAttrInfo>() {
			public int compare(DataAttrInfo t1, DataAttrInfo t2) {
				return t1.offset - t2.offset;
			}
		});

		Printer p = new Printer(attributes, relcatBuf.getInt(SM_RelcatRec
				.getAttrCountOffset()));

		// Open relation file
		if ((rc = pRmm.openFile(relName, fh)) != RC.SUCCESS) {
			return rc;
		}
		// Print the header information
		p.printHeader(System.out);

		// Normal Print
		if (useIndexNo < 0) {
			if ((rc = fs.openScan(fh, AttrType.INT, 4, 0, CompOp.NO_OP, null,
					RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
				pRmm.closeFile(fh);
				return rc;
			}

			while ((rc = fs.getNextRec(rec)).getValue() != RC.RM_EOF.getValue()) {
				BytePointer data = new BytePointer();

				if (rc != RC.SUCCESS) {
					fs.closeScan();
					pRmm.closeFile(fh);
					return rc;
				}

				if ((rc = rec.getData(data)) != RC.SUCCESS) {
					fs.closeScan();
					pRmm.closeFile(fh);
					return rc;
				}

				p.print(System.out, data.getArray());
			}

			if ((rc = fs.closeScan()) != RC.SUCCESS) {
				pRmm.closeFile(fh);
				return rc;
			}

		}
		// Sorted Print
		else {
			IndexHandle ih = new IndexHandle();
			IndexScan is = new IndexScan();
			RID rid = new RID();

			if ((rc = pIxm.openIndex(relName, useIndexNo, ih)) != RC.SUCCESS) {
				pRmm.closeFile(fh);
				return rc;
			}

			if ((rc = is.openScan(ih, null, CompOp.NO_OP,
					RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
				pIxm.closeIndex(ih);
				pRmm.closeFile(fh);
				return rc;
			}

			while ((rc = is.getNextEntry(rid)) != RC.IX_EOF) {
				BytePointer data = new BytePointer();

				if (rc != RC.SUCCESS) {
					is.closeScan();
					pIxm.closeIndex(ih);
					pRmm.closeFile(fh);
					return rc;
				}

				if ((rc = fh.getRec(rid, rec)) != RC.SUCCESS) {
					is.closeScan();
					pIxm.closeIndex(ih);
					pRmm.closeFile(fh);
					return rc;
				}

				if ((rc = rec.getData(data)) != RC.SUCCESS) {
					is.closeScan();
					pIxm.closeIndex(ih);
					pRmm.closeFile(fh);
					return rc;
				}

				p.print(System.out, data.getArray());
			}

			if ((rc = is.closeScan()) != RC.SUCCESS) {
				pIxm.closeIndex(ih);
				pRmm.closeFile(fh);
				return rc;
			}
			if ((rc = pIxm.closeIndex(ih)) != RC.SUCCESS) {
				pRmm.closeFile(fh);
				return rc;
			}
		}

		// Print the footer information
		p.printFooter(System.out);
		// Close relation file
		if ((rc = pRmm.closeFile(fh)) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	} // print relName contents

	public RC set(String paramName, String value) {
		if (paramName.compareToIgnoreCase("useindex") == 0)
			useIndexNo = Integer.parseInt(value);
		else
			return (RC.SM_PARAMUNDEFINED);

		// Return ok
		return RC.SUCCESS;
	} // value

	// Private

	/**
	 * @param relName
	 *            : in
	 * @param rec
	 *            : out
	 * @param data
	 *            : out
	 */
	private RC getRelationInfo(String relName, RM_Record rec, BytePointer data) {
		RC rc;
		byte[] _relNameBuf = new byte[RedBase.MAXNAME];
		String _relName = null;
		RM_FileScan fs = new RM_FileScan();

		// Open a file scan for RELCAT
		Arrays.fill(_relNameBuf, (byte) 0);
		System.arraycopy(relName.getBytes(Charset.forName("ASCII")), 0,
				_relNameBuf, 0, Math.min(relName.length(), RedBase.MAXNAME));
		_relName = new String(_relNameBuf, Charset.forName("ASCII"));
		if ((rc = fs.openScan(fhRelcat, AttrType.STRING, RedBase.MAXNAME,
				SM_RelcatRec.getRelNameOffset(), CompOp.EQ_OP, _relName,
				RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
			return rc;
		}

		// Find the matching record
		if ((rc = fs.getNextRec(rec)) != RC.SUCCESS) {
			if (rc.getValue() == RC.RM_EOF.getValue()) {
				rc = RC.SM_RELNOTFOUND;
			}
			fs.closeScan();
			return rc;
		} else {
			if ((rc = rec.getData(data)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
		}

		// Close a file scan for RELCAT
		if ((rc = fs.closeScan()) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	}

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param value
	 *            : in
	 * @return
	 */
	private RC setRelationIndexCount(String relName, int value) {
		RC rc;
		RM_Record rec = new RM_Record();
		BytePointer relcatData = new BytePointer();

		if ((rc = getRelationInfo(relName, rec, relcatData)) != RC.SUCCESS) {
			return rc;
		}

		// Update indexCount
		ByteBuffer buf = ByteBuffer.wrap(relcatData.getArray());
		int indexCount = buf.getInt(SM_RelcatRec.getIndexCountOffset());
		indexCount += value;
		buf.putInt(SM_RelcatRec.getIndexCountOffset(), indexCount);

		if ((rc = fhRelcat.updateRec(rec)) != RC.SUCCESS) {
			return rc;
		}
		if ((rc = fhRelcat.forcePages(RedBase.ALL_PAGES)) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	}

	/**
	 * 
	 * @param relName
	 *            : in
	 * @param attrName
	 *            : in
	 * @param rec
	 *            : out
	 * @param data
	 *            : out
	 * @return
	 */
	private RC getAttributeInfo(String relName, String attrName, RM_Record rec,
			BytePointer data) {
		RC rc;
		byte[] _relattrNameBuf = new byte[RedBase.MAXNAME * 2];
		String _relattrName = null;

		RM_FileScan fs = new RM_FileScan();

		// Open a file scan for ATTRCAT
		Arrays.fill(_relattrNameBuf, (byte) 0);
		System
				.arraycopy(relName.getBytes(Charset.forName("ASCII")), 0,
						_relattrNameBuf, 0, Math.min(relName.length(),
								RedBase.MAXNAME));
		System.arraycopy(attrName.getBytes(Charset.forName("ASCII")), 0,
				_relattrNameBuf, RedBase.MAXNAME, Math.min(relName.length(),
						RedBase.MAXNAME));
		_relattrName = new String(_relattrNameBuf, Charset.forName("ASCII"));
		if ((rc = fs.openScan(fhAttrcat, AttrType.STRING, RedBase.MAXNAME * 2,
				SM_AttrcatRec.getRelNameOffset(), CompOp.EQ_OP, _relattrName,
				RedBase.ClientHint.NO_HINT)) != RC.SUCCESS) {
			return rc;
		}

		// Find the matching record
		if ((rc = fs.getNextRec(rec)) != RC.SUCCESS) {
			if (rc.getValue() == RC.RM_EOF.getValue()) {
				rc = RC.SM_ATTRNOTFOUND;
			}
			fs.closeScan();
			return rc;
		} else {
			if ((rc = rec.getData(data)) != RC.SUCCESS) {
				fs.closeScan();
				return rc;
			}
		}

		// Close a file scan for ATTRCAT
		if ((rc = fs.closeScan()) != RC.SUCCESS) {
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	}

	private IX_Manager pIxm;
	private RM_Manager pRmm;
	private RM_FileHandle fhRelcat;
	private RM_FileHandle fhAttrcat;
	private int useIndexNo;

}
